﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SIA.HwAccess.PLCSiemens
{    
        /// <summary>Indirizzo di tipo S7</summary>
        internal class S7HwAddress : IHwAddress
        {
            private int _dbNum = 0;
            private int _startByte = 0;
            private int _bit = 0;
            private int _lenByte = 0;
            private eDataType _dataType = eDataType.NONE;
            private eAreaType _areaType = eAreaType.NONE;

            //evento di per messaggi di notifica
            //public event MsgNotifier msgNotifierEvent;

            /// <summary>Costruttore</summary>
            /// <param name="key"></param>
            public S7HwAddress(String str)
            {
                this.parse(str);
            }
            public S7HwAddress(String str, eAreaType areaType)
                :this(str)
            {
                if (areaType!= eAreaType.NONE)
                    _areaType = areaType;
            }
            public S7HwAddress(IHwAddress hwAddress)
            {
                _dbNum = hwAddress.DataBlock;
                _startByte = hwAddress.StartByte;
                _lenByte = hwAddress.LenByte;
                _bit = hwAddress.Bit;
                _dataType = hwAddress.DataType;
                _areaType = hwAddress.AreaType;
            }
            public S7HwAddress(int dB, int startByte, int lenByte, int bit, eAreaType areaType, eDataType dataType)
            {
                _dbNum = dB;
                _startByte = startByte;
                _lenByte = lenByte;
                _bit = bit;
                _dataType = dataType;
                _areaType = areaType;
            }

            #region Implementazione Interfaccia

            public String DataKey { get { return _areaType.ToString() + (_dbNum > 0 ? _dbNum.ToString() : ""); } }

            public int DataBlock
            {
                get { return this._dbNum; }
            }
            public int StartByte
            {
                get { return this._startByte; }
            }
            public int LenByte
            {
                get { return this._lenByte; }
            }
            public eDataType DataType
            {
                get { return this._dataType; }
            }
            public int Bit
            {
                get { return this._bit; }
            }
            public eAreaType AreaType
            {
                get { return this._areaType; }
            }


            private eDataType char2DataType(char c)
            {
                switch (c)
                {
                    case 'X':
                        return eDataType.S7BOOLEAN;
                    case 'B':
                        return eDataType.S7BYTE;
                    case 'W':
                        return eDataType.S7WORD;
                    case 'D':
                        return eDataType.S7DWORD;
                    default:
                        return eDataType.NONE;
                }
            }

            private int dataType2Len(eDataType dt)
            {
                switch (dt)
                {
                    case eDataType.S7BOOLEAN:
                        return 0;
                    case eDataType.S7BYTE:
                        return 1;
                    case eDataType.S7INT:
                    case eDataType.S7WORD:
                        return 2;
                    case eDataType.S7DWORD:
                    case eDataType.S7DINT:
                        return 4;
                    default:
                        return this._lenByte;
                }
            }

            /// <summary>Traduce una stringa in un indirizzo
            /// throw new Exception("S7HwAddress.parse: Indirizzo " + key + " non valido! " + ex.Message);
            /// </summary>
            /// <param name="key">Esempi: 
            /// "DBx.DBXy.z", "DBx.DBBy", "DBx.DBWy"    Area DataBlock specifica 
            /// "DBXx.y",  "DBBy", "DBWy"               Senza Area DataBlock (per protocollo RAW)
            /// "Mx.y",  "MBy", "MWy"                   Area Merker (Ancora non supportato nei protocolli)
            /// "Ex.y",  "EBy", "EWy"                   Area Ingressi (Ancora non supportato nei protocolli)
            /// "Ax.y",  "ABy", "AWy"                   Area Uscite (Ancora non supportato nei protocolli)
            /// "PAx",  "PEx"                           Area Ingessi e usite decentrate (Ancora non supportato nei protocolli)
            /// "Tx",  "Zx"                             Area Timer e contatori (Ancora non supportato nei protocolli)
            /// </param>
            /// <returns></returns>
            public void parse(string str)
            {
                str = str.Trim().ToUpper();
                string[] strSplit = str.Split(new String[] { "." }, StringSplitOptions.RemoveEmptyEntries); // Divido la stringa e tolgo le parti inutili//TODO
                try
                {
                    switch (str[0])
                    {
                        case 'D':
                            if (str[1] != 'B') throw new Exception("IHwAddress: Indirizzo non valido");
                            _dataType = char2DataType(str[2]);
                            if (_dataType != eDataType.NONE) //quindi nel terso carattere ho un carattere: X,B,W,D
                            {
                                _areaType = eAreaType.NONE;
                                this._startByte = int.Parse(strSplit[0].Substring(3));
                                if (_dataType == eDataType.S7BOOLEAN)
                                {
                                    this._bit = int.Parse(strSplit[1]);
                                }
                            }
                            else
                            {
                                _areaType = eAreaType.DB;
                                this._dbNum = int.Parse(strSplit[0].Substring(2));
                                _dataType = char2DataType(strSplit[1][2]);
                                this._startByte = int.Parse(strSplit[1].Substring(3));
                                if (_dataType == eDataType.S7BOOLEAN)
                                {
                                    this._bit = int.Parse(strSplit[2]);
                                }
                            }
                            break;
                        case 'M':
                        case 'E':
                        case 'A':
                            this._areaType = (eAreaType)Enum.Parse(typeof(eAreaType), str[0].ToString());
                            _dataType = char2DataType(str[1]);
                            if (_dataType == eDataType.NONE)
                            {
                                this._dataType = eDataType.S7BOOLEAN;
                                this._startByte = int.Parse(strSplit[0].Substring(1));
                                this._bit = int.Parse(strSplit[1]);
                            }
                            else
                            {
                                this._startByte = int.Parse(strSplit[0].Substring(2));
                            }
                            break;
                        case 'T':
                        case 'Z':
                            this._areaType = (eAreaType)Enum.Parse(typeof(eAreaType), str[0].ToString());
                            this._startByte = int.Parse(strSplit[0].Substring(2));
                            break;
                        default:
                            throw new Exception("Tipo non supportato");//TODO Exception
                    }
                    _lenByte = dataType2Len(this.DataType);

                }
                catch (Exception ex)
                {
                    throw new Exception("S7HwAddress.parse: Indirizzo " + str + " non valido! " + ex.Message);
                }

            }

            public override string ToString()
            {
                string strByte = "";  //Stringa per Tipo di Dato

                // Tipo di Dato
                switch (this._dataType)
                {
                    case eDataType.S7BOOLEAN:
                        strByte = "X" + this._startByte.ToString() + "." + this._bit.ToString();
                        break;
                    case eDataType.S7BYTE:
                        strByte = "B" + this._startByte.ToString();
                        break;
                    case eDataType.S7INT:
                    case eDataType.S7WORD:
                        strByte = "W" + this._startByte.ToString();
                        break;

                    case eDataType.S7DINT:
                    case eDataType.S7DWORD:
                        strByte = "D" + this._startByte.ToString();
                        break;
                    default:
                        strByte = "X" + this._startByte.ToString() + "." + this._bit.ToString() + " BYTE " + LenByte.ToString();
                        break;
                }
                if (_areaType == eAreaType.DB)
                    return "DB" + this._dbNum.ToString() + ".DB" + strByte;
                else
                {
                    strByte = strByte[1] != 'X' ? strByte : strByte.Substring(1);
                    return _areaType.ToString() + strByte;
                }

            }


            public Boolean Contains(IHwAddress hwAddress)
            {
                if (_areaType != hwAddress.AreaType) return false;
                if (_dbNum != hwAddress.DataBlock) return false;

                if ( _dataType == eDataType.S7BOOLEAN)
                    return (_startByte == hwAddress.StartByte && _bit == hwAddress.Bit);
                else
                    return (_startByte <= hwAddress.StartByte &&
                        _startByte + _lenByte >= hwAddress.StartByte + hwAddress.LenByte);
            }

            public Boolean Adjacent(IHwAddress hwAddress)
            {
                if (_areaType != hwAddress.AreaType) return false;
                if (_dbNum != hwAddress.DataBlock) return false;

                if (_dataType == eDataType.S7BOOLEAN)
                {
                    if (_bit == 0)
                        return (_startByte == hwAddress.StartByte && hwAddress.Bit == 1) ||
                            (_startByte == (hwAddress.StartByte - 1) && hwAddress.Bit == 7);
                    if (_bit == 7)
                        return (_startByte == hwAddress.StartByte && hwAddress.Bit == 6) ||
                           (_startByte == (hwAddress.StartByte + 1) && hwAddress.Bit == 0);
                    return this.Contains(hwAddress);
                }
                else
                {
                    return (        //Fine di hwa contenuto in this
                                (hwAddress.StartByte + hwAddress.LenByte + 1)>= _startByte && 
                                    (hwAddress.StartByte + hwAddress.LenByte + 1) <= _startByte + _lenByte
                            ) || (  //Inizio di hwa contenuti in this
                               (hwAddress.StartByte - 1)>= _startByte && 
                                    (hwAddress.StartByte - 1) <= _startByte + _lenByte
                            ) || (  //this contenuti in hwa
                                hwAddress.Contains(this)
                            );                        
                }
            }

            public void Merge(IHwAddress hwAddress)
            {
                if (this.Contains(hwAddress)) return;

                _dataType = eDataType.S7ANY;
                
                int lastbyte = (_startByte + _lenByte) > (hwAddress.StartByte + hwAddress.LenByte)? (_startByte + _lenByte) : (hwAddress.StartByte + hwAddress.LenByte);                   

                _startByte = _startByte < hwAddress.StartByte ? _startByte : hwAddress.StartByte;
                _lenByte = (lastbyte - _startByte) == 0 ? 1 : (lastbyte - _startByte);
            }

            #endregion


            public IHwAddress Round2Word()
            {
                if (_dataType == eDataType.S7BOOLEAN) _dataType = eDataType.S7ANY;                
                _startByte = (_startByte % 2) == 1 ? _startByte - 1 : _startByte;
                _lenByte = (_lenByte % 2) == 1 ? _lenByte + 1 : _lenByte;
                _lenByte = _lenByte <= 0 ? 2 : _lenByte;

                return this;
            }
        }
}
